package com.kamistoat.meimeimall.common.interceptor;

import com.kamistoat.meimeimall.common.constant.AuthConstant;
import com.kamistoat.meimeimall.common.threadlocal.ThreadLocalContextHolder;
import com.kamistoat.meimeimall.common.utils.JwtUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Collections;

/**
 * 所有模块的登录令牌拦截器。
 * 只负责根据请求头携带的 JwtString 解析结果去缓存中拿出登录令牌，转存到ThreadLocal中。
 * 由于Gateway也依赖了common模块，可以在Gateway中排除这个类的加载。
 * <p></p>
 * 排序-100，比网关的认证拦截器{@link sun.net.httpserver.AuthFilter}级别低
 */
@Component
@Order(-100)
public class GlobalLoginInterceptor implements HandlerInterceptor {
    protected Logger logger = LoggerFactory.getLogger(GlobalLoginInterceptor.class);

    @Autowired
    StringRedisTemplate stringRedisTemplate;

    /**
     * 前置拦截。如果请求头中存在解析出来的 JwtString，则根据其直接去缓存中获取用户登录令牌转存ThreadLocal
     */
    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
        onlyForDebug(httpServletRequest, httpServletResponse, o);
//        String userToken = httpServletRequest.getHeader(AuthConstant.JWT_USER_TOKEN);
//        String userId = httpServletRequest.getHeader(AuthConstant.JWT_USER_ID);
//        if (!StringUtils.isEmpty(userToken) && !StringUtils.isEmpty(userId)) {
//            String loginSuccessToString = stringRedisTemplate.opsForValue().get(AuthConstant.LOGIN_USER_REDIS_PREFIX + userId + ":" + userToken);
//            if (!StringUtils.isEmpty(loginSuccessToString)) {
//                ThreadLocalContextHolder.set(AuthConstant.LOGIN_USER_THREADLOCAL_NAME, loginSuccessToString);
//            }
//        }
        return true;
    }

    /**
     * 只在开发阶段使用。开发阶段直连模块进行测试，不会走网关。所以网关的前置拦截器要拿过来在这里做。
     *
     * @param httpServletRequest
     * @param httpServletResponse
     * @param o
     */
    private void onlyForDebug(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) {
        // 获取响应式请求中携带的 Authorization 字段即 jwtString
        String jwtString = httpServletRequest.getHeader(AuthConstant.JWT_AUTHENTICATION);
        // 如果没有携带 Authorization ，则直接放行
        if (StringUtils.isEmpty(jwtString)) {
            // logger.info("前端未携带Jwt令牌");
        } else {
            if (jwtString.startsWith(AuthConstant.JWT_PREFIX)) {
                jwtString = jwtString.replaceFirst(AuthConstant.JWT_PREFIX, "");
            }
            // 从JwtString中解析出三个构造用的三个字段
            String userId = JwtUtils.getUserIdFromJwtString(jwtString);
            String userToken = JwtUtils.getUserTokenFromJwtString(jwtString);
            String userName = JwtUtils.getUserNameFromJwtString(jwtString);

            if (StringUtils.isEmpty(userId) || StringUtils.isEmpty(userToken) || StringUtils.isEmpty(userName)) {
                logger.info("前端携带的Jwt令牌已过期或无法使用指定秘钥正确解析Jwt令牌，删除Jwt令牌");
            } else {
                // 根据Jwt解析的字段去缓存中查询。查询使用LUA脚本，在查询的同时延长过期时间
                String luaScript = "if redis.call('exists', KEYS[1]) then \n" +
                        "redis.call('expire', KEYS[1], ARGV[1]); \n" +
                        "return 1; \n" +
                        "else \n" +
                        "return 0; \n" +
                        "end; \n";
                Long execute = stringRedisTemplate.execute(
                        new DefaultRedisScript<Long>(luaScript, Long.class),
                        Collections.singletonList(getTokenCacheKey(userId, userToken)),
                        String.valueOf(AuthConstant.LOGIN_USER_REDIS_EXPIRE)
                );

                if (execute == null || execute.equals(0L)) {
                    logger.info("登录状态已经过期，或您的核心权限发生变更。请重新登录以获取最新权限");
                } else {
                    String loginSuccessToString = stringRedisTemplate.opsForValue().get(getTokenCacheKey(userId, userToken));
                    if (!StringUtils.isEmpty(loginSuccessToString)) {
                        ThreadLocalContextHolder.set(AuthConstant.LOGIN_USER_THREADLOCAL_NAME, loginSuccessToString);
                    }
                }
            }
        }
    }

    private String getTokenCacheKey(String userId, String userKey) {
        return AuthConstant.LOGIN_USER_REDIS_PREFIX + userId + ":" + userKey;
    }

    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
    }

    /**
     * 请求完全结束后，清空ThreadLocal
     */
    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
        ThreadLocalContextHolder.remove();
    }
}
